/**
  ******************************************************************************
  * @file    btnlib.h
  * @author  siyun.chen
  * @version v1.0.0
  * @date    
  * @brief   嵌入式按钮处理库
  ******************************************************************************
  * Change Logs:
  * 2025-10-27      siyun.chen      first version
  ******************************************************************************
  *
  * MIT License
  *
  * Copyright (c) 2025 siyun.chen
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in all
  * copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
  ******************************************************************************
  */

#ifndef _BTNLIB_H_
#define _BTNLIB_H_

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

/* 按需配置 */
#define BTN_PROCESS_PERIOD 10                                   // 处理周期(ms)，即调用btn_process处理函数的周期
#define BTN_DEBOUNCE_NUM 2                                      // 连续多少次扫描读到不同电平才会更新状态(1 ~ 15)
#define BTN_LONG_PRESS_THRESHOLD (1500 / BTN_PROCESS_PERIOD)    // 长按阈值(ms)，即超过多长时间视为长按
#define BTN_DOUBLE_HIT_THRESHOLD (300 / BTN_PROCESS_PERIOD)     // 双击阈值(ms)，即两次短按间隔不超过多少时间视为双击
#define BTN_LONG_PRESS_HOLD_PERIOD (500 / BTN_PROCESS_PERIOD)   // 长按保持事件触发周期(ms)
#define BTN_STATE_TIMEOUT_THRESHOLD (60000 / BTN_PROCESS_PERIOD)// 状态机超时阈值(ms)，防止状态机卡死

/**
 * PRESSED与RELEASED事件：
 *      1、只要发生按下动作(单击、双击、长按、……)，就会触发一次PRESSED事件。(双击中的每次按下都会触发一次PRESSED事件)
 *      2、只要发生释放动作(单击、双击、长按、……)，就会触发一次RELEASED事件。(双击中的每次释放都会触发一次RELEASED事件)
 *
 * SINGLE与DOUBLE事件：
 *      1、如果没有通过btn_add_callback添加DOUBLE事件，一次短按操作直接视为SINGLE事件。(快，释放后立即触发SINGLE事件)
 *      2、双击检测过程中，双击阈值内仅发生一次短按操作，视为SINGLE事件。(慢，释放后仍需等待双击阈值时间之后才触发SINGLE事件)
 *      3、双击检测过程中，双击阈值内连续发生两次短按操作，视为DOUBLE事件。
 *      4、双击检测过程中，如果第二击按键时间超过长按阈值，仅视为LONG_PRESS事件，忽略第一次短按事件，但PRESSED与RELEASED依旧会触发两次。
 *
 * LONG_PRESS事件：
 *      1、到达长按时间阈值LONG_TICKS，则会触发一次LONG_PRESS_START事件
 *      2、随后每隔BTN_LONG_PRESS_HOLD_PERIOD时间，都会触发一次LONG_PRESS_HOLD事件
 *      3、长按结束时，会触发一次LONG_PRESS_STOP事件
 */
typedef enum
{
    BTN_EVENT_PRESSED = 0,      // 按压事件
    BTN_EVENT_RELEASED,         // 释放事件
    BTN_EVENT_SINGLE,           // 单击事件(短按)
    BTN_EVENT_DOUBLE,           // 双击事件(短按 + 短按)
    BTN_EVENT_LONG_PRESS_START, // 长按开始
    BTN_EVENT_LONG_PRESS_HOLD,  // 长按保持
    BTN_EVENT_LONG_PRESS_STOP,  // 长按结束
    BTN_EVENT_TOTAL,            // 事件数量
    BTN_EVENT_NONE,             // 无事件
} btn_event_t;

typedef struct btn_t btn_t;
typedef void (*btn_callback_func)(btn_t *obj, btn_event_t event);

struct btn_t
{
    uint8_t debounce_cnt : 4;               // 消抖计数器
    uint8_t state : 4;                      // 状态机状态
    uint8_t current_pressed : 1;            // 当前按键是否被按下(经过消抖处理)
    uint16_t scan_cnt;                      // 扫描计数器
    bool (*read_io_pressed)(void *context); // 读取按键是否被按下函数
    void *context;                          // 读取函数的上下文参数
    btn_callback_func cb[BTN_EVENT_TOTAL];  // 触发事件回调函数数组，每个元素对应一种事件类型
    btn_t *next;                            // 指向下一节点
};

/**
 * 简单使用示例：
 * 
 * 1. 定义按钮对象和IO读取函数
 * 
 * btn_t button1;
 * 
 * bool read_button1_pressed(void *context) {
 *     // 返回按键是否被按下，true表示按下，false表示释放
 *     return HAL_GPIO_ReadPin(BUTTON1_GPIO_Port, BUTTON1_Pin) == GPIO_PIN_RESET;
 * }
 * 
 * 2. 注册按钮并添加事件回调
 * 
 * btn_register(&button1, read_button1_pressed, NULL);
 * 
 * btn_add_callback(&button1, BTN_EVENT_SINGLE, button1_single_callback);
 * btn_add_callback(&button1, BTN_EVENT_DOUBLE, button1_double_callback);
 * btn_add_callback(&button1, BTN_EVENT_LONG_PRESS_START, button1_long_press_start_callback);
 * 
 * 3. 在主循环中定期调用处理函数
 * 
 * while(1) {
 *     btn_process();
 *     HAL_Delay(BTN_PROCESS_PERIOD); // 确保处理周期为BTN_PROCESS_PERIOD
 * }
 * 
 * 4. 实现回调函数
 * 
 * void button1_single_callback(btn_t *obj, btn_event_t event) {
 *     // 处理单击事件
 *     printf("Button1 单击\n");
 * }
 * 
 * void button1_double_callback(btn_t *obj, btn_event_t event) {
 *     // 处理双击事件
 *     printf("Button1 双击\n");
 * }
 * 
 * void button1_long_press_start_callback(btn_t *obj, btn_event_t event) {
 *     // 处理长按开始事件
 *     printf("Button1 长按开始\n");
 * }
 * 
 * 注意事项：
 * - 确保btn_process()以固定周期BTN_PROCESS_PERIOD调用
 * - 每个按钮对象需要独立的btn_t实例
 * - 支持多按钮同时使用，通过链表管理
 * - 事件回调函数中不要执行耗时操作
 */

void btn_register(btn_t *obj, bool (*read_io_pressed)(void *context), void *context);
void btn_unregister(btn_t *obj);
void btn_add_callback(btn_t *obj, btn_event_t event, btn_callback_func cb);
void btn_process(void);

#endif
